﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace OCRTheNewHope
{
    public static class Synthesizer
    {
        public static double[,] Generate2DRotationMatrix(double degree)
        {
            double[,] rVal = new double[2, 2];

            double radian = degree.ToRadian();

            rVal[0, 0] = Math.Cos(radian);
            rVal[0, 1] = -1 * Math.Sin(radian);
            rVal[1, 0] = Math.Sin(radian);
            rVal[1, 1] = Math.Cos(radian);

            return rVal;
        }

        public static double[,] Generate2DAffineMatrix(double affineAngle, double[,] affineMatrix)
        {
            double[,] rotationMatrix1 = Generate2DRotationMatrix(affineAngle);
            double[,] rotationMatrix2 = Generate2DRotationMatrix(-1 * affineAngle);

            double[,] rVal = rotationMatrix1.Multiply(affineMatrix).Multiply(rotationMatrix2);

            return rVal;
        }
        
        static byte Interpolate(byte[,] matrix, double x, double y) // check if it's in the matrix
        {
            byte rVal = 0;

            double xDiff;
            double yDiff;

            int[] leftUpper = new int[2];

            leftUpper[0] = (int)x;
            leftUpper[1] = (int)y;

            xDiff = Math.Abs(x - leftUpper[0]);
            yDiff = Math.Abs(y - leftUpper[1]);
            
            if (leftUpper[0] < 27 && leftUpper[1] < 27 && leftUpper[0] >= 0 && leftUpper[1] >= 0)
            {
                double[] coeffs = new double[4];

                coeffs[0] = (1 - xDiff) * (1 - yDiff);
                coeffs[1] = (1 - yDiff) * xDiff;
                coeffs[2] = xDiff * yDiff;
                coeffs[3] = (1 - xDiff) * yDiff;

                rVal = (byte)(coeffs[0] * matrix[leftUpper[1], leftUpper[0]] + coeffs[1] * matrix[leftUpper[1], leftUpper[0] + 1] + coeffs[2] * matrix[leftUpper[1] + 1, leftUpper[0] + 1] + coeffs[3] * matrix[leftUpper[1] + 1, leftUpper[0]]);
            }
            else if ((leftUpper[1] == 27 && leftUpper[0] == 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]]);
            }
            else if ((leftUpper[0] == 27 && leftUpper[1] >= 0 && leftUpper[1] < 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]] * xDiff + matrix[leftUpper[1] + 1, leftUpper[0]] * (1 - xDiff));
            }
            else if ((leftUpper[1] == 27 && leftUpper[0] >= 0 && leftUpper[0] < 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]] * yDiff + matrix[leftUpper[1], leftUpper[0] + 1] * (1 - yDiff));
            }

            return rVal;
        }

        private static Digit ApplyTransformation(int newDigitID, Digit sourceDigit, double[,] transformationMatrix)
        {
            Digit rVal = null;

            int offset = sourceDigit.OriginalGrayscaleMatrix.GetLength(0) / 2;

            double[,] inverseTransformationMatrix = transformationMatrix.Inverse2DMatrix();

            byte[,] newImage = new byte[sourceDigit.OriginalGrayscaleMatrix.GetLength(0), sourceDigit.OriginalGrayscaleMatrix.GetLength(1)];

            for (int i = 0; i < sourceDigit.OriginalGrayscaleMatrix.GetLength(0); i++)
            {
                for (int j = 0; j < sourceDigit.OriginalGrayscaleMatrix.GetLength(1); j++)
                {
                    int y = -offset + i;
                    int x = j - offset;

                    double[] newCoords = GetNewCoords(new double[] { x, y }, inverseTransformationMatrix);

                    double calculatedY = newCoords[1] + offset; 
                    double calculatedX = newCoords[0] + offset;

                    newImage[i, j] = Interpolate(sourceDigit.OriginalGrayscaleMatrix, calculatedX, calculatedY);
                }
            }

            rVal = new Digit(newDigitID, sourceDigit.DigitValue, newImage);

            return rVal;
        }

        private static double[] GetNewCoords(double[] coords, double[,] inverseTransformationMatrix)
        {
            double[] rVal = coords.Multiply(inverseTransformationMatrix);

            return rVal;
        }

        public static double[][,] GenerateTransformationMatrices()
        {
            double[][,] rVal = new double[27][,];

            int index = 0;

            double[] RotationAngles = { 0, -7, 7 };
            double[] AffineAngles = { 0, -15, 15 };

            double[][,] affineMatrices = new double[][,] 
            {
                new double[,] { { 1, 0 } , { 0, 1 }   },
                new double[,] { { 1.05, 0.0 },  { 0.0, 0.95 }  }, 
                new double[,] { { 0.95, 0.0 } , { 0.0, 1.05 }  } 
            };
            
            foreach (double rotationAngle in RotationAngles)
            {
                foreach (double affineAngle in AffineAngles)
                {
                    foreach (var affineMatrixCoeff in affineMatrices)
                    {
                        double[,] rotationMatrix = Generate2DRotationMatrix(rotationAngle);
                        double[,] affineMatrix = Generate2DAffineMatrix((double)affineAngle, affineMatrixCoeff);

                        rVal[index] = rotationMatrix.Multiply(affineMatrix);

                        index++;
                    }
                }
            }

            return rVal;
        }

        public static void Synthesize(Digit[] digits)
        {
            var transformationMatrices = GenerateTransformationMatrices();

            BinaryWriter b = FileOperations.CreateHeader("C:\\BIG-heavy.data", digits.Length * transformationMatrices.Length + 4, 28, 28);
            
            Digit[] temp = new Digit[digits.Length];

            int digitID = 60000;

            for (int i = 0; i < transformationMatrices.Length; i++)
            {
                for (int j = 0; j < digits.Length; j++)
                {
                    temp[j] = ApplyTransformation(digitID, digits[j], transformationMatrices[i]);
                    digitID++;
                }

                Console.WriteLine(i + "th chunk is finished");

                FileOperations.WriteDoubleIntegralDatabase(b, temp);
            }
        }
    }
}
